/*
* Copyright (c) 2001 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

/**
* @file
* video decoding with libavcodec API example
*
* @example decode_video.c
*/
#include "YDecoder.h"

YDecoder::YDecoder(MySDLWindow* pMyWnd)
	:m_pMsg(nullptr)
	,m_bExit(0)
{
	m_pMySDLWnd = pMyWnd;
	m_pMsg = new YMessageList();
	m_pMsg->Init();

	pkt = av_packet_alloc();
	if (!pkt)
		exit(1);

	/* find the MPEG-1 video decoder */
	codec = avcodec_find_decoder(AV_CODEC_ID_MPEG2VIDEO);
	if (!codec) {
		YLOG("Codec not found\n");
		exit(1);
	}

	parser = av_parser_init(codec->id);
	if (!parser) {
		YLOG("parser not found\n");
		exit(1);
	}

	c = avcodec_alloc_context3(codec);
	if (!c) {
		YLOG("Could not allocate video codec context\n");
		exit(1);
	}

	/* For some codecs, such as msmpeg4 and mpeg4, width and height
	MUST be initialized there because this information is not
	available in the bitstream. */

	/* open it */
	//av_opt_set(c->priv_data, "preset", "ultrafast", 0);
	//av_opt_set(c->priv_data, "tune", "stillimage,fastdecode,zerolatency", 0);
	if (avcodec_open2(c, codec, NULL) < 0) {
		YLOG("Could not open codec\n");
		exit(1);
	}

	frame = av_frame_alloc();
	if (!frame) {
		YLOG("Could not allocate video frame\n");
		exit(1);
	}
}

YDecoder::~YDecoder()
{
	if (m_pMsg)
	{
		m_pMsg->Release();
		delete m_pMsg;
		m_pMsg = nullptr;
	}
	sws_freeContext(sc);
	av_parser_close(parser);
	avcodec_free_context(&c);
	av_frame_free(&frame);
	av_packet_free(&pkt);
}

int YDecoder::DecodeToYuv(uint8_t* h264_data, int h264_len,
	uint8_t* yuv_data, int* yuv_len, int* width, int* height)
{
	uint8_t *data = h264_data;
	size_t   data_size = h264_len;
	while (data_size > 0)
	{
		int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
			data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
		if (ret < 0) {
			YLOG("Error while parsing\n");
			return -1;
		}
		data += ret;
		data_size -= ret;

		if (pkt->size)
			DecodeInternal(yuv_data, yuv_len, width, height, AV_PIX_FMT_YUV420P);
	}
	return 0;
}

int YDecoder::DecodeToRGB(uint8_t* h264_data, int h264_len,
	uint8_t* rgb_data, int* rgb_len, int* width, int* height)
{
	uint8_t *data = h264_data;
	size_t   data_size = h264_len;
	while (data_size > 0)
	{
		int ret = av_parser_parse2(parser, c, &pkt->data, &pkt->size,
			data, data_size, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
		if (ret < 0) {
			YLOG("Error while parsing\n");
			return -1;
		}
		data += ret;
		data_size -= ret;

		if (pkt->size)
			DecodeInternal(rgb_data, rgb_len, width, height, AV_PIX_FMT_BGR24);
	}
	return 0;
}

void YDecoder::ProcessMessage()
{
	if (!m_pMySDLWnd)
		return;

	const char* fname_h264 = "recv.h264";
	FILE* fh264 = nullptr;
	errno_t err = fopen_s(&fh264, fname_h264, "wb");
	if (err)
	{
		printf("Can not open file %s\n", fname_h264);
		return;
	}

	uint8_t* yuv_data = new uint8_t[1280 * 720 * 3];
	int yuv_len = 0;
	int pack_count = 0;
	int width, height;
	while (!m_bExit)
	{
		if (!m_pMsg->IsEmpty())
		{
			std::lock_guard<std::mutex> guard(m_mutex);
			int len = m_pMsg->GetNextLength();
			uint8_t* data = new uint8_t[len];
			pack_count++;
			m_pMsg->GetData(data, len);
			fwrite(data, 1, len, fh264);
			if (len == 4 && data[3] && 0x80) //end of h264 file
			{
				m_bExit = 1;
			}
			auto ts = std::chrono::system_clock::now();
			DecodeToYuv(data, len, yuv_data, &yuv_len, &width, &height);
			if (yuv_len)
			{
				m_pMySDLWnd->SendMessage(yuv_data, yuv_len);
				auto te = std::chrono::system_clock::now();
				long long duration = std::chrono::duration_cast<std::chrono::microseconds>(te - ts).count();
				//YLOG("decode-----end duration=%lld\n", duration);
			}
			yuv_len = 0;
			delete[]data;
		}
	}
	delete []yuv_data;

	printf("-----------------------------pack_count=%d\n", pack_count);
	fclose(fh264);
}

void YDecoder::SendMessage(uint8_t* data, const size_t len)
{
	std::lock_guard<std::mutex> guard(m_mutex);
	m_pMsg->AddData(data, len);
}

int YDecoder::YuvToRGB(uint8_t * rgb_data, int* rgb_len, int* width, int* height)
{
	uint8_t *dst_data[4];
	int dst_linesize[4];
	if (av_image_alloc(dst_data, dst_linesize, frame->width, frame->height,
		AV_PIX_FMT_BGR24, 1) < 0) {
		return -1;
	}

	sc = sws_getContext(frame->width, frame->height, (AVPixelFormat)c->pix_fmt, frame->width,
		frame->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr);
	int ret = sws_scale(sc, frame->data, frame->linesize, 0, frame->height, dst_data, dst_linesize);
	memcpy(rgb_data, dst_data[0], frame->width * frame->height* 3);

	*width = frame->width;
	*height = frame->height;
	*rgb_len = frame->width*frame->height * 8;
	return 0;
}

void YDecoder::DecodeInternal(uint8_t* data, int* len, int* width, int* height, AVPixelFormat format)
{
	int ret;
	using namespace std;
	auto ts = std::chrono::system_clock::now();
	ret = avcodec_send_packet(c, pkt);
	if (ret < 0) {
		YLOG("Error sending a packet for decoding\n");
		exit(1);
	}

	while (ret >= 0) {
		ret = avcodec_receive_frame(c, frame);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
			return;
		else if (ret < 0) {
			YLOG("Error during decoding\n");
			exit(1);
		}

		fflush(stdout);
		std::chrono::system_clock::time_point te;
		long long duration;
		switch (format)
		{
		case AV_PIX_FMT_BGR24:
			YuvToRGB(data, len, width, height);

			/*te = std::chrono::system_clock::now();
			duration = std::chrono::duration_cast<std::chrono::microseconds>(te - ts).count();
			YLOG("decode-----end duration=%lld\n", duration);*/
			break;
		default:
			YuvSave(data, len);

			te = std::chrono::system_clock::now();
			duration = std::chrono::duration_cast<chrono::microseconds>(te-t).count();
			t = te;
			YLOG("decode-----one frame time=%lld  frame num=%3d\n", duration, c->frame_number);
			break;
		}
	}
}

//save to yuv buf
void YDecoder::YuvSave(uint8_t* yuv_buf, int* yuv_len)
{
	int width = frame->width;
	int height = frame->height;
	int linesize0 = frame->linesize[0];
	int linesize1 = frame->linesize[1];
	int linesize2 = frame->linesize[2];
	*yuv_len = width * height * 3 / 2;
	
	int i;

	//save to yuv buf, here we just drop padding data
	for (i = 0; i < height; i++)
	{
		memcpy(yuv_buf+i*width, frame->data[0] + i * linesize0, width);
	}
	for (i = 0; i < height / 2; i++)
	{
		memcpy(yuv_buf + width*height+i*width/2, frame->data[1]+i*linesize1, width/2);
	}

	for (i = 0; i < height / 2; i++)
	{
		memcpy(yuv_buf + width*height*5/4+i*width/2, frame->data[2]+i*linesize2, width/2);
	}
}

